3. Queued Service-Side Programming
The service needs to construct a proxy that will
dispatch messages to the client-side response service. To simplify
this, use my ServiceResponseBase<T>, defined
as:
public abstract class ServiceResponseBase<T> : HeaderClientBase<T,ResponseContext>
where T : class
{
public ServiceResponseBase();
public ServiceResponseBase(string bindingName);
public ServiceResponseBase(NetMsmqBinding binding);
}
ServiceResponseBase<T>
automates reading the response context from the message headers and
writing the response itself to the outgoing headers. Other than that,
you can use it like any other proxy base class. Example 9-25 demonstrates the usage of
ServiceResponseBase<T>.
Example 5. Using ServiceResponseBase<T>
class CalculatorResponseClient : ServiceResponseBase<ICalculatorResponse>,
ICalculatorResponse
{
public void OnAddCompleted(int result,ExceptionDetail error)
{
Channel.OnAddCompleted(result,error);
}
}
class MyCalculator : ICalculator
{
[OperationBehavior(TransactionScopeRequired = true)]
public void Add(int number1,int number2)
{
int result = 0;
ExceptionDetail error = null;
try
{
result = number1 + number2;
}
//Don't rethrow
catch(Exception exception)
{
error = new ExceptionDetail(exception);
}
finally
{
CalculatorResponseClient proxy = new CalculatorResponseClient();
proxy.OnAddCompleted(result,error);
proxy.Close();
}
}
}
|
In Example 5, the MyCalculator service catches any exception
thrown by the business logic operation and wraps that exception with
an ExceptionDetail object. The
service does not rethrow the exception. As you will see later, in the
context of transactions and response services, rethrowing the
exception would also cancel the response. Moreover, when using a
response service, being able to respond in case of an error is a much
better strategy than relying on WCF’s playback error handling.
In the finally statement,
regardless of exceptions, the service responds. It creates a new proxy
to the response service to enqueue the response. The proxy in Example 5 will default to using the
same MSMQ binding as the host.
Example 6 shows the
implementation of ServiceResponseBase<T>.
Example 6. Implementing ServiceResponseBase<T>
public abstract class ServiceResponseBase<T> : HeaderClientBase<T,ResponseContext>
where T : class
{
public ServiceResponseBase() : this(OperationContext.Current.Host.
Description.Endpoints[0].Binding as NetMsmqBinding)
{}
public ServiceResponseBase(string bindingName) :
this(new NetMsmqBinding(bindingName))
{}
public ServiceResponseBase(NetMsmqBinding binding) :
base(ResponseContext.Current,binding,
new EndpointAddress(ResponseContext.Current.ResponseAddress))
{
Endpoint.VerifyQueue();
}
}
|
The default constructor of ServiceResponseBase<T> uses the same
queued binding the host was using to dequeue the client’s call. You
can also specify an MSMQ binding section in the config file or provide
the constructor with the binding instance to use. Both of these
constructors delegate the work to the third constructor, which accepts
the MSMQ binding to use. That constructor reads the response address
out of the response context and provides those two along with the
response context to the base constructor of HeaderClientBase<T,H>. It also
verifies the presence of the response queue.
Note that ServiceResponseBase<T> sends the
response service the entire response context (not just the ID). This
is done both for simplicity’s sake and because it may be beneficial
for the response service to have access to the fault and response
address used.
4. Response Service-Side Programming
The response service accesses its response context, reads from
it the method ID, and responds accordingly. Example 7 demonstrates a possible
implementation of such a response service.
Example 7. Implementing a response service
class MyCalculatorResponse : ICalculatorResponse
{
[OperationBehavior(TransactionScopeRequired = true)]
public void OnAddCompleted(int result,ExceptionDetail error)
{
string methodId = ResponseContext.Current.MethodId;
...
}
}
|
Note:
It is common for the response service to update the
application’s user interfaces with the queued results (or errors). For
example:
class CalculatorResponse :
FormHost<CalculatorResponse>,
ICalculatorResponse
{
[OperationBehavior(TransactionScopeRequired=true)]
public void OnAddCompleted(int result,
ExceptionDetail error)
{
Text = "Add returned: " + result;
...
}
}
In fact, nothing prevents you from having the client itself be
the response service as well.